﻿#region using statements

using System;
using System.Collections.Generic;

#endregion

namespace NGen.Managers.GameObject.Fsm
{
	public abstract class GameObject2DFsm<TStateMachineInterfaceType> : GameObject2D
		where TStateMachineInterfaceType : GameObject2DFsm<TStateMachineInterfaceType>
	{
		#region local fields

		/// <summary>
		/// 	the state stack
		/// </summary>
		private readonly Stack<GameObject2DFsmState<TStateMachineInterfaceType>> m_stateStack = 
			new Stack<GameObject2DFsmState<TStateMachineInterfaceType>>();

		#endregion

		#region properties

		/// <summary>
		/// returns the top state on the stack
		/// </summary>
		public GameObject2DFsmState<TStateMachineInterfaceType> CurrentState {
			get {
				if(m_stateStack.Count == 0)
					throw new NullReferenceException("State stack is empty");
				return m_stateStack.Peek();
			}
		}

		#endregion

		#region public methods

		/// <summary>
		/// 	push a new state on the stack and call OnEnter method
		/// </summary>
		/// <param name = "p_newState">the state to push</param>
		public void Push(GameObject2DFsmState<TStateMachineInterfaceType> p_newState) {
			NotifyOnExit();

			m_stateStack.Push(p_newState);
			p_newState.Parent = this as TStateMachineInterfaceType;
			p_newState.Initialize();

			NotifyOnEnter();
		}

		/// <summary>
		/// 	pop the top state from the stop and call on Exit. Repeated for the entire stack if true is passed.
		/// </summary>
		/// <param name = "p_clearStack">should the stack be cleared</param>
		/// <returns>was anything successfully popped</returns>
		public bool Pop(bool p_clearStack = false) {
			if(m_stateStack.Count == 0) {
				return false;
			}
			
			NotifyOnExit();
			if(p_clearStack) {
				while(m_stateStack.Count > 0) {
					
					m_stateStack.Pop().Cleanup();
				}
			} else {
				m_stateStack.Pop().Cleanup();
			}

			NotifyOnEnter();

			return true;
		}

		/// <summary>
		/// 	pop the top state from the statck, then push a new one
		/// </summary>
		/// <param name = "p_newState">the state to push</param>
		/// <param name = "p_clearStack">should the stack be cleared</param>
		public void Change(GameObject2DFsmState<TStateMachineInterfaceType> p_newState, bool p_clearStack = false) {
			NotifyOnExit();
			
			if(p_clearStack) {
				while(m_stateStack.Count > 0) {
					m_stateStack.Pop().Cleanup();
				}
			} else {
				if(m_stateStack.Count > 0) {
					m_stateStack.Pop().Cleanup();
				}
			}

			m_stateStack.Push(p_newState);
			p_newState.Parent = this as TStateMachineInterfaceType;
			p_newState.Initialize();

			NotifyOnEnter();
		}

		#endregion

		#region protected methods

		/// <summary>
		/// 	notify the top state on the stack that it has become the new active state
		/// </summary>
		protected void NotifyOnEnter() {
			if(m_stateStack.Count > 0) {
				m_stateStack.Peek().OnEnter();
			}
		}

		/// <summary>
		/// 	notify the top state on the stack that it is no longer the active state
		/// </summary>
		protected void NotifyOnExit() {
			if(m_stateStack.Count > 0) {
				m_stateStack.Peek().OnExit();
			}
		}

		#endregion
	}
}